package state

import (
	"errors"
	"alex/ptp1588boundaryclock/datasets"
	"alex/ptp1588boundaryclock/datatypes"
	"alex/ptp1588boundaryclock/com"
	"alex/ptp1588boundaryclock/com/general"
	"time"
)

// =====================================================================================================================
// Single Port
// =====================================================================================================================
type Port struct {
	pHandler *PortHandler
	portDS *datasets.PortDS
	timePropertiesDS *datasets.TimePropertiesDS
	foreignDS          datasets.ForeignDS
	clock *Clock
	announceMsgs       []general.AnnounceMessage
	bestAnnounceMsg    general.AnnounceMessage
	stateDecisionCode  uint8
	announceSequenceID uint16
	// Maybe a buffered channel will turn out to be more efficient
	announceMessageChannel    chan general.AnnounceMessage
	announceSeqCtr            chan bool
	bMCSignalChannel          chan error
	dSUpdateSignalChannel     chan bool
}

// =====================================================================================================================
// Port Data
// =====================================================================================================================
// Initializes a single Port
func (p* Port) Init(pHandler *PortHandler, portDS *datasets.PortDS, timePropertiesDS *datasets.TimePropertiesDS,
	clock *Clock) {
	// 32 Announce Messages should be enough - needs to be tested though
	p.announceMsgs = make([]general.AnnounceMessage, 0, 32)
	p.pHandler = pHandler
	p.portDS = portDS
	p.timePropertiesDS = timePropertiesDS
	p.clock = clock
	p.announceSequenceID = 0
	// if one port is INITIALIZING, all ports shall be as well!
	if p.portDS != nil {
		p.foreignDS.ForeignMasterAnnounceMessages = 0
		p.portDS.PortState = INITIALIZING
		p.announceMessageChannel = make(chan general.AnnounceMessage, 100)
		p.bMCSignalChannel = make(chan error)
		p.dSUpdateSignalChannel = make(chan bool)
		p.announceSeqCtr = make(chan bool, 5)
		go p.portManager()
		p.portDS.PortState = LISTENING
	}
}

// AddAnnounceMessages adds Announce Messages to the slice
func (p* Port) addAnnounceMessage(announce general.AnnounceMessage) {
	p.announceMsgs = append(p.announceMsgs, announce)
	p.foreignDS.ForeignMasterAnnounceMessages++
}

// Merges the slice
func (p* Port) merge() {
	// keeps the previous winner
	p.announceMsgs[0] = p.bestAnnounceMsg
	p.announceMsgs = p.announceMsgs[0:1]
}

// changes the state values
func (p* Port) stateChange() {
	if p.stateDecisionCode == M1 || p.stateDecisionCode == M2 || p.stateDecisionCode == M3 {
		p.portDS.PortState = MASTER
	} else if p.stateDecisionCode == P1 || p.stateDecisionCode == P2 {
		p.portDS.PortState = PASSIVE
	} else if p.stateDecisionCode == S1 {
		p.portDS.PortState = SLAVE
	}
}

// =====================================================================================================================
// BMC
// =====================================================================================================================
// Computes E_rbest as defined in 9.3.2.3
// Consider only qualified messages => filter.go needed in com package
// non-empty set is always deemed better!
func (p* Port) compare(err chan error) {
	if length := len(p.announceMsgs); length >= 2 {
		if p.portDS.PortState == INITIALIZING {
			err <- errors.New("portHandler: At least one port is in INITIALIZING state")
			return
		}
		msgChannel := make(chan general.AnnounceMessage)
		go p.compareAll(p.announceMsgs, msgChannel)
		p.bestAnnounceMsg = <-msgChannel
		//p.BestAnnounceMsg.Winner = true
		p.merge()
	}
	err <- nil
}

// Compares all Announce messages and sends the best on the channel msgChannel
func (p* Port) compareAll(msgs []general.AnnounceMessage, msgChannel chan general.AnnounceMessage) {
	// tmpChannel is used as sub-channel
	tmpChannel := make(chan general.AnnounceMessage)
	if length := len(msgs); length <= 5 {
		go p.comparePart(msgs, tmpChannel)
		msgChannel <- <-tmpChannel
	} else {
		// slice is split to enable concurrent processing
		ctr := length / 5
		tmpMsgs := make([]general.AnnounceMessage, 0, ctr)
		for i := 0; i < ctr; i++ {
			go p.comparePart(msgs[i*5:], tmpChannel)
		}
		for i := 0; i < ctr; i++ {
			tmpMsgs = append(tmpMsgs, <-tmpChannel)
		}
		p.compareAll(tmpMsgs, msgChannel)
	}
}

// Compares only a part of the slice => concurrency
// Sends the best message on the channel msgChannel
func (p* Port) comparePart(msgs []general.AnnounceMessage, msgChannel chan general.AnnounceMessage) {
	bestMsg := msgs[0]
	if len(msgs) > 1 {
		// No more than 5 messages are compared at each pass...
		for i := 1; i < len(msgs) && i < 5; i++ {
			if setA, _ := DataSetComparison(&bestMsg, &msgs[i]); setA {
				bestMsg = msgs[i]
			}
		}
	}
	msgChannel <- bestMsg
}

// Figure 26 - State Decision Algorithm
func (p* Port) stateDecision(best *general.AnnounceMessage) {
	if !(p.bestAnnounceMsg.ParentDS == nil && p.portDS.PortState == LISTENING) {
		if p.pHandler.defaultDS.ClockQuality.ClockClass > 0 && p.pHandler.defaultDS.ClockQuality.ClockClass < 128 {
			if d0, _ := DataSetComparison(p.pHandler.defaultDS, &p.bestAnnounceMsg); d0 {
				p.stateDecisionCode = M1
			} else {
				p.stateDecisionCode = P1
			}
		} else {
			if d0, _ := DataSetComparison(p.pHandler.defaultDS, best); d0 {
				p.stateDecisionCode = M2
			} else {
				if *best == p.bestAnnounceMsg {
					p.stateDecisionCode = S1
				} else {
					if tmp, _ := DataSetComparison(best, &p.bestAnnounceMsg); tmp {
						p.stateDecisionCode = P2
					} else {
						p.stateDecisionCode = M3
					}
				}
			}
		}
	}
}

// 9.3.5 Update of Data Sets
func (p* Port) dataSetUpdate() {
	// Table 13 - Updates for state decision code M1 and M2
	if p.stateDecisionCode == M1 || p.stateDecisionCode == M2 {
		// 9.4. Grandmaster Clock
		if p.timePropertiesDS.PtpTimescale && (p.pHandler.defaultDS.ClockQuality.ClockClass == 6 ||
			p.pHandler.defaultDS.ClockQuality.ClockClass == 7 || p.pHandler.defaultDS.ClockQuality.ClockClass == 52) {
			// value obtained from primary reference
			p.timePropertiesDS.Leap59 = false
			p.timePropertiesDS.Leap61 = false
			// TODO: 7.2.3
			p.timePropertiesDS.CurrentUtcOffset = 0
			p.timePropertiesDS.CurrentUtcOffsetValid = false
			// p.timePropertiesDS.PtpTimescale = true
			p.timePropertiesDS.TimeTraceable = false
			p.timePropertiesDS.FrequencyTraceable = false
			p.timePropertiesDS.TimeSource = datasets.INTERNAL_OSCILLATOR
		} else if p.pHandler.defaultDS.ClockQuality.ClockClass != 6 && p.pHandler.defaultDS.ClockQuality.ClockClass != 7 &&
			p.pHandler.defaultDS.ClockQuality.ClockClass != 52 {
			p.timePropertiesDS.Leap59 = false
			p.timePropertiesDS.Leap61 = false
			// TODO: 7.2.3
			p.timePropertiesDS.CurrentUtcOffset = 0
			p.timePropertiesDS.CurrentUtcOffsetValid = false
			// p.timePropertiesDS.PtpTimescale = false
			p.timePropertiesDS.TimeTraceable = false
			p.timePropertiesDS.FrequencyTraceable = false
			p.timePropertiesDS.TimeSource = datasets.INTERNAL_OSCILLATOR
		}
	} else if p.stateDecisionCode == S1 {
		p.timePropertiesDS.CurrentUtcOffset = p.pHandler.superAnnounceMessage.TimePropertiesDS.CurrentUtcOffset
		p.timePropertiesDS.CurrentUtcOffsetValid = p.pHandler.superAnnounceMessage.TimePropertiesDS.CurrentUtcOffsetValid
		p.timePropertiesDS.Leap59 = p.pHandler.superAnnounceMessage.TimePropertiesDS.Leap59
		p.timePropertiesDS.Leap61 = p.pHandler.superAnnounceMessage.TimePropertiesDS.Leap61
		p.timePropertiesDS.TimeTraceable = p.pHandler.superAnnounceMessage.TimePropertiesDS.TimeTraceable
		p.timePropertiesDS.FrequencyTraceable = p.pHandler.superAnnounceMessage.TimePropertiesDS.FrequencyTraceable
		p.timePropertiesDS.PtpTimescale = p.pHandler.superAnnounceMessage.TimePropertiesDS.PtpTimescale
		p.timePropertiesDS.TimeSource = p.pHandler.superAnnounceMessage.TimePropertiesDS.TimeSource
	}
}

// =====================================================================================================================
// Concurrency
// =====================================================================================================================
// Manages the general behaviour of the associated port
// @Why: To improve coordination with clock
func (p* Port) portManager() {
	// Timer is here for testing purposes
	timer := time.After(60 * time.Millisecond)
	for {
		select {
			// receive messages as usual
		case announce := <-p.announceMessageChannel:
			p.addAnnounceMessage(announce)
			// start bmc comparison
		case <-p.bMCSignalChannel:
			tmpErr := make(chan error)
			go p.compare(tmpErr)
			p.bMCSignalChannel <- <-tmpErr
			// start data set updatetvit
		case <-p.dSUpdateSignalChannel:
			p.dataSetUpdate()
			p.stateChange()
		p.dSUpdateSignalChannel <- true
			// eliminates a highly unlikely race condition
		case <-p.announceSeqCtr:
			p.announceSequenceID++
		case <-timer:
			go p.sendMessage(*p.pHandler.defaultDS, *p.pHandler.currentDS, *p.pHandler.parentDS, *p.portDS,
				*p.timePropertiesDS, com.Announce, p.announceSequenceID)
			timer = time.After(60 * time.Millisecond)
		}
	}
}

// =====================================================================================================================
// Messages
// =====================================================================================================================
// Send an announce message
// Data sets are passed by value in order to make concurrency easier -> fire and forget
// Because of this 'fire and forget' practice the sequenceID is increased via a channel
func (p* Port) sendMessage(defaultDS datasets.DefaultDS, currentDS datasets.CurrentDS, parentDS datasets.ParentDS,
	portDs datasets.PortDS, timePropertiesDS datasets.TimePropertiesDS,
	msgType com.MessageType, SequenceID uint16) {
	msgSlice := make([]byte, msgType.GetMessageLength())
	msgHeader := &com.Header{&defaultDS, &portDs, &timePropertiesDS, msgType, 0, SequenceID}
	// Message is Interface -> msgText := new(Message) => pointer to interface! bad!
	var msgText com.Message

	// Maybe function for this...
	// TODO: other messages
	if msgType == com.Announce {
		msgText = &general.AnnounceMessage{currentDS.StepsRemoved, &parentDS,
			&timePropertiesDS, new(datatypes.TimeStamp), &msgHeader.PortDS.PortIdentity}
		p.announceSeqCtr <- true
	}

	done := make(chan bool)
	go msgHeader.Write(msgSlice[:34], done)
	go msgText.Write(msgSlice[34:], done)

	if <-done && <-done {
	}
	// 1320? admin rights
	go com.MessageSender("224.0.0.129", "1320", msgSlice)
}
